﻿using System.CodeDom;
using System.ComponentModel;

namespace InterfaceWeaver.CustomTool
{
    public static class IEditableHelper
    {
        /// <summary>
        /// Implements the I editable object.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="useINotifyPropertyChangedImplementation">if set to <c>true</c> [use I notify property changed implementation].</param>
        /// <returns></returns>
        public static CodeTypeDeclaration ImplementIEditableObject(this CodeTypeDeclaration type, bool useINotifyPropertyChangedImplementation = false)
        {
            if (!type.IsInheriting<IEditableObject>())
            {
                type.BaseTypes.Add(new CodeTypeReference(typeof(IEditableObject)));
                var beginEditMethod = new CodeMemberMethod
                {
                    Name = "BeginEdit",
                    Attributes = MemberAttributes.Public,
                };

                var endEditMethod = new CodeMemberMethod
                {
                    Name = "EndEdit",
                    Attributes = MemberAttributes.Public
                };

                var cancelEditMethod = new CodeMemberMethod
                {
                    Name = "CancelEdit",
                    Attributes = MemberAttributes.Public
                };

                beginEditMethod.Comments.Add(new CodeCommentStatement(@"<summary>Begins an edit on an object.</summary>", true));
                endEditMethod.Comments.Add(new CodeCommentStatement(@"<summary>Pushes changes since the last <see cref=""M:System.ComponentModel.IEditableObject.BeginEdit"" /> or <see cref=""M:System.ComponentModel.IBindingList.AddNew"" /> call into the underlying object.</summary>", true));
                cancelEditMethod.Comments.Add(new CodeCommentStatement(@"<summary>Discards changes since the last <see cref=""M:System.ComponentModel.IEditableObject.BeginEdit"" /> call.</summary>", true));

                // editionHasBegun field definition
                var isEditingField = new CodeMemberField(typeof(bool), "editionHasBegun")
                {
                    Attributes = MemberAttributes.Private
                };

                // EditionHasBegun property definition
                var isEditingProperty = new CodeMemberProperty
                {
                    Name = "EditionHasBegun",
                    Type = new CodeTypeReference(typeof(bool)),
                    HasGet = true,
                    HasSet = false
                };

                isEditingProperty.GetStatements.Add(new CodeMethodReturnStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name)));

                beginEditMethod.Statements.AddRange(new CodeStatement[]
                    {
                        new CodeSnippetStatement(string.Empty),
                        new CodeCommentStatement("Activating edition status.")
                    });
                var beginEditMethodStatmentsCore = new CodeAssignStatement(
                            new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name),
                            new CodePrimitiveExpression(true));

                endEditMethod.Statements.AddRange(new CodeStatement[]
                    {
                        new CodeSnippetStatement(string.Empty),
                        new CodeCommentStatement("Reseting edition status.")
                    });
                var endEditMethodStatmentsCore = new CodeAssignStatement(
                            new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name),
                            new CodePrimitiveExpression(false));

                cancelEditMethod.Statements.AddRange(new CodeStatement[]
                    {
                        new CodeSnippetStatement(string.Empty),
                        new CodeCommentStatement("Reseting edition status.")
                    });
                var cancelEditMethodStatmentsCore = new CodeAssignStatement(
                            new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name),
                            new CodePrimitiveExpression(false));

                // NotifyProperty has changed...
                if (useINotifyPropertyChangedImplementation)
                {
                    type.ImplementINotifyPropertyChanged();

                    var beginEditCheckValueChangedCondition = new CodeConditionStatement(
                        new CodeSnippetExpression(string.Format("!this.{0}", isEditingField.Name)));
                    beginEditCheckValueChangedCondition.TrueStatements.Add(beginEditMethodStatmentsCore);
                    beginEditCheckValueChangedCondition.TrueStatements.AddRange(isEditingProperty.CodeRaisePropertyChangedEventSequence());

                    var endEditCheckValueChangedCondition = new CodeConditionStatement(
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name));
                    endEditCheckValueChangedCondition.TrueStatements.Add(endEditMethodStatmentsCore);
                    endEditCheckValueChangedCondition.TrueStatements.AddRange(isEditingProperty.CodeRaisePropertyChangedEventSequence());

                    var cancelEditCheckValueChangedCondition = new CodeConditionStatement(
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), isEditingField.Name));
                    cancelEditCheckValueChangedCondition.TrueStatements.Add(cancelEditMethodStatmentsCore);
                    cancelEditCheckValueChangedCondition.TrueStatements.AddRange(isEditingProperty.CodeRaisePropertyChangedEventSequence());

                    beginEditMethod.Statements.Add(beginEditCheckValueChangedCondition);
                    endEditMethod.Statements.Add(endEditCheckValueChangedCondition);
                    cancelEditMethod.Statements.Add(cancelEditCheckValueChangedCondition);
                }
                else
                {
                    beginEditMethod.Statements.Add(beginEditMethodStatmentsCore);
                    endEditMethod.Statements.Add(endEditMethodStatmentsCore);
                    cancelEditMethod.Statements.Add(cancelEditMethodStatmentsCore);
                }

                type.Members.Add(isEditingField);
                type.Members.Add(isEditingProperty);

                type.Members.Add(beginEditMethod);
                type.Members.Add(endEditMethod);
                type.Members.Add(cancelEditMethod);
            }

            return type;
        }
    }
}
